<?php

/**
 * Application model for Cake.
 *
 * This file is application-wide model file. You can put all
 * application-wide model-related methods here.
 *
 * PHP versions 5
 * CAKEPHP versions 2.x
 * 
 * Green CMS - Content Management System and Framework Powerfull by Cakephp
 * Copyright 2012, GREEN GLOBAL CO., LTD (toancauxanh.vn)
 * 
 * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
 * Copyright 2005-2011, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @author        Technology Lab No.I <tech1@toancauxanh.vn>
 * @link          
 * @package       Green.Model
 * @since         Green v 1.0
 * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
 */
App::uses('Model', 'Model');

/**
 * Application model for Cake.
 *
 * Add your application-wide methods in the class below, your models
 * will inherit them.
 *
 * @package       Green.Model
 */
class AppModel extends Model {

    /**
     * Whether or not to cache queries for this model.  This enables in-memory
     * caching only, the results are not stored beyond the current request.
     *
     * @var boolean
     */
    public $cacheQueries = true;

    /**
     * Defined to cache config to use
     * 
     * @var string
     * @see Model::cache
     */
    public $cacheConfig = 'default';

    /**
     * Defined to enable cache config
     * 
     * @var boolean
     * @see Model::cache
     */
    public $useCache = false;

    /**
     * List of behaviors to load when the model object is initialized. Settings can be
     * passed to behaviors by using the behavior name as index. 
     * 
     * If you should translate table, you can use Translator behavior
     * i.e. public $actsAs = array('Translator'=>array('key1','key2')); 
     * or   public $actsAs = array('Translator');
     *
     * Note : Don't uses Translate and Containable behavior if your has used Translator behavior
     * 
     * @var array
     * @see Model::$actsAs
     * @see Model::translateFields
     * @see TranslatorBehavior
     */
    public $actsAs = array();

    /**
     * Timestamp Unix maps
     *
     * @var array
     */
    var $timestamp = array();

    /**
     * Locale will translate data , set it be for use translate if you want translate orther language.
     * default is current locale of language
     * 
     *
     * @var string
     * @see TranslatorBehavior::translate
     */
    public $locale = null;

    /**
     * Defined model using for I18n
     * 
     * @var string
     * @see TranslatorBehavior::translateModel
     */
    public $translateModel = null;

    /**
     * Defined fields using for I18n
     * 
     * @var array
     * @see TranslatorBehavior::setup
     */
    public $translateFields = array();

    /**
     * Defined fields using serialize and unserialized
     * 
     * @var array
     * @see SerializableBehavior::setup
     */
    public $serializeFields = array();

    /**
     * Backup runtime of TranslatorBehavior
     * 
     * @var array
     * @see TranslatorBehavior::localize
     */
    public $__backOriginalTranslator = array();

    /**
     * Backup runtime of cache cache enabled
     * 
     * @var mixed
     * @see AppModel::cache
     */
    public $__backOriginalCache = null;

    /**
     * Defined to load the config file stored
     * 
     * @var string config key
     */
    public $configKey = null;

    /**
     * Storage last message
     * 
     * @var string
     */
    protected $_message = null;

    /**
     * Overrides Model::__construct
     *
     * Constructor. Load customeize config for this model.
     *
     * @return void
     * @see Model::__construct
     */
    public function __construct($id = false, $table = null, $ds = null) {
        if ($this->configKey) {
            Configure::load('Model.' . $this->configKey);
            foreach ((array) Configure::read($this->configKey) as $key => $config) {
                if (!isset($this->{$key}) || !is_array($this->{$key})) {
                    $this->{$key} = $config;
                } else {
                    $this->{$key} = Set::merge($this->{$key}, (array) $config);
                }
            }
        }
        parent::__construct($id, $table, $ds);
    }

    /**
     * Deconstructs a complex data type (array or object) into a single field value.
     *
     * @param string $field The name of the field to be deconstructed
     * @param mixed $data An array or object to be deconstructed into a field
     * @return mixed The resulting data that should be assigned to a field
     */
    public function deconstruct($field, $data) {
        $_schema = $this->schema();
        if (isset($this->timestamp[$field])) {
            $this->_schema[$field]['type'] = $this->timestamp[$field];
        }
        $data = parent::deconstruct($field, $data);
        $this->_schema = $_schema;
        return $data;
    }

    /**
     * Before save callback
     *
     * Convert string datetime to timestamp Unix
     *
     * @return boolean
     */
    public function beforeSave($options = array()) {
        if ($this->timestamp && !empty($this->data[$this->alias])) {
            foreach ($this->timestamp as $field => $type) {
                if (!empty($this->data[$this->alias][$field]) && !is_int($this->data[$this->alias][$field])) {
                    $this->data[$this->alias][$field] = strtotime($this->data[$this->alias][$field]);
                }
            }
        }
        return true;
    }

    /**
     * Before delete callback
     *
     * Check data will be dependent delete if 'dependent' in hasOne or hasMany defined false
     *
     * @return boolean true if not found relatiron data , fasle on found
     */
    public function beforeDelete($cascade = true) {
        if ($cascade) {
            foreach (array_merge($this->hasOne, $this->hasMany) as $assoc => $data) {
                if (isset($data['dependent']) && $data['dependent'] === false
                        && ($found = $this->{$assoc}->find('count', array(
                    'conditions' => array($data['foreignKey'] => $this->id),
                    'recursive' => -1)))
                ) {
                    $this->setMessage(__d('green', 'Cannot delete row #%1$s. Please delete %2$s dependent %3$s that before delete it.', array(
                                $this->id,
                                $found,
                                __d('green', Inflector::humanize(Inflector::underscore($assoc))))));
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Get or set last error message
     * 
     * @param string $message string if to set or undefined to get message
     *
     * @return void
     */
    public function setMessage($message) {
        $this->_message = (string) $message;
    }

    /**
     * Get or set last error message
     *
     * @return string message
     */
    public function getMessage() {
        $message = $this->_message;
        $this->_message = null;
        return $message;
    }

    /**
     * Overrides Model::find
     *
     * Allow cache using cache data when cacheKey is set
     *
     * @todo if use contain get only model associated $belongsTo then set recursive=2
     * @return mixed array Array of records or int for find $type count
     * @see Model::find
     */
    public function find($type = 'first', $query = array()) {
        $cacheKey = $this->useCache;
        if (!is_null($this->__backOriginalCache)) {
            $this->useCache = $this->__backOriginalCache;
            $this->__backOriginalCache = null;
        }
        if ($cacheKey) {
            $Behavior = $this->Behaviors->Translator;
            if (($Behavior || ($Behavior = $this->Behaviors->Containable))
                    && isset($Behavior->runtime[$this->alias])) {
                $cacheKey = serialize(array($query, $Behavior->runtime[$this->alias]));
            } else {
                $cacheKey = serialize($query);
            }
            $cacheKey = $this->alias . '_find_' . crc32($type . $cacheKey);
            $results = Cache::read($cacheKey, $this->cacheConfig);
            if ($results !== false) {
                return $results;
            }
        }

        $results = parent::find($type, $query);

        if ($cacheKey) {
            Cache::write($cacheKey, $results, $this->cacheConfig);
        }
        return $results;
    }

    /**
     * 
     * Green CMS was override new features to this function
     * 
     * Returns a list of fields from the database, and sets the current model
     * data (Model::$data) with the record found.
     *
     * @param array $query the list fields or Option fields (conditions / fields / joins / limit / offset / order / page / group / callbacks)
     * @param mixed $id The ID of the record to read
     * @return array Array of database fields, or false if not found
     * @ignore Model::read
     */
    public function read($query = array(), $id = null) {
        $this->validationErrors = array();

        if ($id != null) {
            $this->id = $id;
        }

        $id = $this->id;

        if (is_array($this->id)) {
            $id = $this->id[0];
        }

        if ($id !== null && $id !== false) {
            if (Set::numeric($query)) {
                $query = array('fields' => $query);
            }
            if (!isset($query['conditions'])) {
                $query['conditions'] = array();
            }
            $query['conditions'] = array_merge($query['conditions'], array(
                $this->alias . '.' . $this->primaryKey => $id));
            return $this->find('first', $query);
            //$this->data = $this->find('first', $query);
            //return $this->data;
        } else {
            return false;
        }
    }

    /**
     * Auto get unique value of field
     *
     * @param string $field
     * @param string $value
     * @param string $replacement
     * @see string unique value
     */
    public function getUnique($field, $value, $replacement = '_') {
        $unique = $_unique = strtolower(Inflector::slug($value, $replacement));
        $next = 1;

        while ($this->find('count', array(
            'recursive' => -1,
            'callbacks' => false,
            'conditions' => array($this->alias . '.' . $field => $unique))) > 0) {
            $unique = $_unique . $replacement . $next;
            $next++;
        }

        return $unique;
    }

    /**
     * Use cache data for Model::find.
     * 
     * {{{
     *    $Model->cache(true);
     *    //Or
     *    $Model->cache('cache_config');
     *    $Model->find('all');
     *    //Or
     *    $Model->cache(array('enabled'=>true,'config'=>'config_cache'))->find('all');
     * }}}
     *
     * @param mixed $config
     *  - boolean to config enabled cache
     *  - string to config cache config name
     *  - array to config by key
     * @param boolean $reset
     * @return Model Object
     * @see Model::find
     */
    public function cache($enabled = true, $reset = true) {
        if ($reset) {
            $this->__backOriginalCache = $this->useCache;
        }
        $this->useCache = (boolean) $enabled;
        return $this;
    }

    /**
     * Set contain config autoFields
     *
     * @todo : sau này sẽ kiểm tra xem hệ thống có chạy chậm hơn khi lấy qua quan hệ không, nếu có thì hỗ trợ autofieds = false luôn
     * @return Model Object
     */
    public function autoFields($set = false) {
        $Behavior = $this->Behaviors->Translator;
        if (($Behavior || ($Behavior = $this->Behaviors->Containable))) {
            $Behavior->settings[$this->alias]['autoFields'] = $set;
        }
        return $this;
    }

    /**
     * Tree depth validate
     *
     * {{{
     *    public $validate = array(
     *      'parent_id'=>array(
     *        'rule' => array('isParentExist')
     *      )
     *    );
     * }}}
     * 
     * Search foreign key records is found
     * 
     * @param array $field one Field/value pairs to search
     * @param integer $field one Field/value pairs to search
     *
     * @return boolean true if depth is valid, false on invalidate
     */
    public function isParentExist($field, $strict = false) {
        list($field) = array_values($field);
        $Tree = $this->Behaviors->Tree;
        if (!$Tree || empty($Tree->settings[$this->alias])) {
            return false;
        }
        extract($Tree->settings[$this->alias] + array('depth' => null));
        if (is_null($depth) || ((!$strict || $depth > 0) && (is_null($field) || $field === ''))) {
            return true;
        }
        if ($depth === 0) {
            return false;
        }
        $item = $this->find('first', array(
            'callbacks' => false,
            'conditions' => array($this->primaryKey => $field),
            'fields' => array($left, $right), 'recursive' => -1));
        if ($item) {
            $item = $item[$this->alias];
        } else {
            return false;
        }
        $count = $this->find('count', array(
            'callbacks' => false,
            'conditions' => array($scope, $left . ' <' => $item[$left], $right . ' >' => $item[$right]),
            'recursive' => -1));

        if ($count >= $depth - 1) {
            return false;
        }
        return true;
    }

    /**
     * Validate function
     *
     * {{{
     *    public $validate = array(
     *      'group_id'=>array(
     *        'rule' => array('isForeignExist','Group', 0)
     *      )
     *    );
     * }}}
     * 
     * Search foreign key records is found
     * 
     * @param array $field one Field/value pairs to search
     * @param array $belongs list model belongto foreign key
     * @param mixel $allowed to don't search value in it
     *
     * @return boolean true of records found , false on records not found
     */
    public function isForeignExist($field, $belongs = array(), $allowed = array()) {
        $fieldName = key($field);
        $field = $field[$fieldName];

        foreach ((array) $belongs as $assoc) {
            if (in_array($field, $allowed)) {
                continue;
            }
            if (!$this->{$assoc}->find('count', array(
                        'callbacks' => false,
                        'conditions' => array($this->{$assoc}->primaryKey => $field),
                        'recursive' => -1))
            ) {
                return false;
            }
        }
        return true;
    }

    /**
     * Deletes multiple model records based on a set of conditions.
     *
     * @param mixed $conditions Conditions to match
     * @param boolean $cascade Set to true to delete records that depend on this record
     * @param boolean $callbacks Run callbacks
     * @return boolean True on success, false on failure
     * @link http://book.cakephp.org/2.0/en/models/deleting-data.html#deleteall
     */
    public function deleteAll($conditions, $cascade = true, $callbacks = false) {
        if (empty($conditions)) {
            return false;
        }
        $db = $this->getDataSource();

        if (!$cascade && !$callbacks) {
            return $db->delete($this, $conditions);
        } else {
            $ids = $this->find('all', array_merge(array(
                        'fields' => "{$this->alias}.{$this->primaryKey}",
                        'recursive' => 0), compact('conditions'))
            );
            if (empty($ids)) {
                return false;
            }

            $ids = Set::extract($ids, "{n}.{$this->alias}.{$this->primaryKey}");

            if (empty($ids)) {
                return true;
            }

            if ($callbacks) {
                $_id = $this->id;
                $result = true;
                foreach ($ids as $id) {
                    $result = ($result && $this->delete($id, $cascade));
                }
                $this->id = $_id;
                return $result;
            } else {
                foreach ($ids as $id) {
                    $this->_deleteLinks($id);
                    if ($cascade) {
                        $this->_deleteDependent($id, $cascade);
                    }
                }
                return $db->delete($this, array($this->alias . '.' . $this->primaryKey => $ids));
            }
        }
    }
    //remove som field not need validate
    function unbindValidation($type, $fields, $require=false) 
    { 
        if ($type === 'remove') 
        { 
            $this->validate = array_diff_key($this->validate, array_flip($fields)); 
        } 
        else 
        if ($type === 'keep') 
        { 
            $this->validate = array_intersect_key($this->validate, array_flip($fields)); 
        } 
         
        if ($require === true) 
        { 
            foreach ($this->validate as $field=>$rules) 
            { 
                if (is_array($rules)) 
                { 
                    $rule = key($rules); 
                     
                    $this->validate[$field][$rule]['required'] = true; 
                } 
                else 
                { 
                    $ruleName = (ctype_alpha($rules)) ? $rules : 'required'; 
                     
                    $this->validate[$field] = array($ruleName=>array('rule'=>$rules,'required'=>true)); 
                } 
            } 
        } 
    } 

}
